package cn.sunline.dreamvalue.service.impl;

import cn.sunline.dreamvalue.constant.CommonResult;
import cn.sunline.dreamvalue.constant.DreamValueType;
import cn.sunline.dreamvalue.constant.RedisConstant;
import cn.sunline.dreamvalue.constant.ResultStatus;
import cn.sunline.dreamvalue.dao.DreamAcctOpenDao;
import cn.sunline.dreamvalue.dao.DreamValueDao;
import cn.sunline.dreamvalue.dto.*;
import cn.sunline.dreamvalue.entity.CustDreamValFlow;
import cn.sunline.dreamvalue.exception.DreamValueExcption;
import cn.sunline.dreamvalue.service.DreamValueService;
import cn.sunline.dreamvalue.service.HbaseService;
import cn.sunline.dreamvalue.utils.DreamValueNoGenerateUtil;
import cn.sunline.dreamvalue.utils.RedisOpsUtil;
import cn.sunline.dreamvalue.utils.SubTableUtil;
import org.apache.commons.lang.StringUtils;
import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
  * @title: DreamValueServiceNode
  * @projectName dream-value
  * @description: 梦想值服务流程节点
  * @author 11292
  * @date 2020/5/7 15:02
*/
@Service
public class DreamValueServiceImpl implements DreamValueService {
    public static final Logger log = LoggerFactory.getLogger(DreamValueServiceImpl.class);

    @Autowired
    private DreamValueDao dreamValueDao;

    @Autowired
    private RedisOpsUtil redisOpsUtil;

    @Autowired
    private SubTableUtil subTableUtil;

    @Autowired
    private Redisson redisson;

    @Autowired
    private DreamValueNoGenerateUtil dreamValueNoGenerateUtil;

    @Autowired
    private HbaseService hbaseService;

    @Autowired
    private DreamAcctOpenDao dreamAcctOpenDao;
    /**
     　* @description: 查询客户梦想值
     　* @param [input]
     　* @return cn.sunline.dreamvalue.constant.CommonResult<java.util.Map<java.lang.String,java.lang.String>>
     　* @author 11292
     　* @date 2020/5/7 15:03
     */
    @Override
    public CommonResult<Map<String,String>> query(DreamValueQueryInDTO input) throws Exception {

        //返回参数定义
        Map<String,String> result = new HashMap<String,String>();
        //参数校验
        if(StringUtils.isEmpty(input.getCrdtNo()) && StringUtils.isEmpty(input.getCustomerNo()) && StringUtils.isEmpty(input.getDreamValueNo())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_REQUIRED_FAILED);
        }
        if(StringUtils.isEmpty(input.getType())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_REQUIRED_FAILED);
        }
        if(!input.getType().trim().equals(DreamValueType.DEFALUT_TYPE.getCode())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_VALUE_INCORRECT);
        }
        if(StringUtils.isEmpty(input.getDreamValueNo())){
            String dreamnum = null;
            if(StringUtils.isNotEmpty(input.getCrdtNo())){
                //hbase中查询证件号映射关系
                Map<String,String> certNoMap = hbaseService.queryCertNoRel(input.getCrdtNo());
                if(null != certNoMap && !certNoMap.isEmpty()){
                    dreamnum = certNoMap.get("bi:a2");
                }
            }

            if(StringUtils.isEmpty(dreamnum) && StringUtils.isNotEmpty(input.getCustomerNo())){
                //hbase中查询客户号映射关系
                Map<String,String> customerIdMap = hbaseService.queryCustomerIdRel(input.getCustomerNo());
                if(null != customerIdMap && !customerIdMap.isEmpty()){
                    dreamnum = customerIdMap.get("bi:a2");
                }
            }
            if(StringUtils.isEmpty(dreamnum)){
                throw new DreamValueExcption(ResultStatus.CUSTOMER_NOT_EXISTS_FAILED);
            }
            input.setDreamValueNo(dreamnum);
        }
        //校验梦想账号是否已注销
        if(!dreamAcctIsExist(input.getDreamValueNo())){
            throw new DreamValueExcption(ResultStatus.CUSTOMER_NOT_EXISTS_FAILED);
        }
        //以下业务处理防止并发问题，增加分布式锁
        //初始化返回参数
        result.put("crdtNo",input.getCrdtNo());
        result.put("customerNo",input.getCustomerNo());
        result.put("type",input.getType());
        result.put("dreamValueNo",input.getDreamValueNo());
        //查询redis中是否存在
        String redisKey = RedisConstant.DREAM_VALUE_BALANCE_REIDS_KEY_PRIFIX + input.getDreamValueNo();
        Integer balance = redisOpsUtil.get(redisKey, Integer.class);
        if(null != balance) {
            result.put("dreamValue",balance.toString());
            return CommonResult.success(result);
        }
        //防止缓存穿透，增加分布式锁
        String lockKey = RedisConstant.DISTRIBUTED_LOCK_QUERY_REIDS_KEY_PRIFIX + input.getDreamValueNo();
        RLock rLock = redisson.getLock(lockKey);
        RedissonRedLock redissonLock = new RedissonRedLock(rLock);
        try {
            if (redissonLock.tryLock()) {
                //查询数据库
                balance = dreamValueDao.queryDreamValue(subTableUtil.getTableSuffix(input.getDreamValueNo()),input.getDreamValueNo(),input.getType());
                if(null == balance){
                    throw new DreamValueExcption(ResultStatus.CUSTOMER_NOT_EXISTS_FAILED);
                }
                //先放入redis中
                redisOpsUtil.set(redisKey,balance,60*60, TimeUnit.SECONDS);
            }else{
                balance = redisOpsUtil.get(redisKey, Integer.class);
            }
        }catch (Exception e){
            log.error("查询梦想值发生异常，异常为：{}",e.getMessage());
            throw  e;
        }finally {
            redissonLock.unlock();
        }
        result.put("dreamValue",balance.toString());
        return CommonResult.success(result);
    }

    /**
     　* @description: 查询梦想账号是否被注销
     　* @param [dreamValueNo]
     　* @return java.lang.Boolean
     　* @author 11292
     　* @date 2020/5/12 9:50
     */
    private Boolean dreamAcctIsClosed(String dreamValueNo) throws IOException {
        //校验梦想账号是否已注销
        Map<String,String> dreamAcctMap = hbaseService.queryDreamAcct(dreamValueNo);
        if(null != dreamAcctMap && !dreamAcctMap.isEmpty()){
            String status = dreamAcctMap.get("bi:a1");
            if (null != status && status.equals(DreamValueType.DREAM_ACCT_STATUS_CLOSE.getCode())) {
                return true;
            }
        }
        return false;
    }

    /**
     　* @description: 查询梦想账号是否存在
     　* @param [dreamValueNo]
     　* @return java.lang.Boolean
     　* @author 11292
     　* @date 2020/5/12 9:50
     */
    private Boolean dreamAcctIsExist(String dreamValueNo) throws IOException {
        //校验梦想账号是否存在
        Map<String,String> dreamAcctMap = hbaseService.queryDreamAcct(dreamValueNo);
        if(null != dreamAcctMap && !dreamAcctMap.isEmpty()){
            return true;
        }
        return false;
    }
    /**
     　* @description: 查询梦想值流水
     　* @param [input]
     　* @return cn.sunline.dreamvalue.constant.CommonResult<cn.sunline.dreamvalue.dto.DreamValueDetailOutDTO>
     　* @author 11292
     　* @date 2020/5/7 17:03
     */
    @Override
    public CommonResult<DreamValueDetailOutDTO> queryDetails(DreamValueDetailInDTO input) throws Exception {
        //定义返回数据体
        DreamValueDetailOutDTO outDTO = new DreamValueDetailOutDTO();
       //必填字段校验
        if(StringUtils.isEmpty(input.getDreamValueNo()) || StringUtils.isEmpty(input.getType())){
            return CommonResult.result(ResultStatus.PARAMETER_REQUIRED_FAILED,outDTO);
        }
        //校验梦想账号是否已注销
        if(dreamAcctIsClosed(input.getDreamValueNo())){
            return CommonResult.result(ResultStatus.DREAM_ACCT_STATUS_CLOSED,outDTO);
        }
        //判断缺省参数
        if(null == input.getSize()){
            input.setSize(10);
        }
        //以下为hbase取数逻辑
        outDTO = hbaseService.queryDreamValFlow(input);

        return CommonResult.success(outDTO);
    }

    /**
     　* @description: 调减
     　* @param [input]
     　* @return cn.sunline.dreamvalue.constant.CommonResult<java.util.Map<java.lang.String,java.lang.String>>
     　* @author 11292
     　* @date 2020/5/7 17:52
     */
    @Transactional(rollbackFor = Throwable.class)
    @Override
    public CommonResult<Map<String, String>> subtract(DreamValueSubtractInDTO input) throws Exception{
        //定义返回体
        Map<String,String> result = new HashMap<String,String>();
        //检查入参
        if(StringUtils.isEmpty(input.getDreamValueNo()) || StringUtils.isEmpty(input.getType())
        || StringUtils.isEmpty(input.getTransNo() ) || StringUtils.isEmpty(input.getTransDesc())
        || StringUtils.isEmpty(input.getTransDate()) || StringUtils.isEmpty(input.getTransTime())
        || null == input.getDreamValue()){
            throw new DreamValueExcption(ResultStatus.PARAMETER_REQUIRED_FAILED);
        }
        //增加调减接口，调减梦想值不能为负数校验，防止刷接口
        if(input.getDreamValue() < 0){
            throw new DreamValueExcption(ResultStatus.SUBTRACT_DREAMVALUE_LESS_THAN_0_FAILED);
        }
        if(!input.getType().trim().equals(DreamValueType.DEFALUT_TYPE.getCode())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_VALUE_INCORRECT);
        }
        //增加梦想账号是否存在校验
        if(!dreamAcctIsExist(input.getDreamValueNo())){
            throw new DreamValueExcption(ResultStatus.CUSTOMER_NOT_EXISTS_FAILED);
        }
        //以下业务处理防止并发问题，增加分布式锁
        String lockKey = RedisConstant.DISTRIBUTED_LOCK_SUBTRACT_REIDS_KEY_PRIFIX + input.getType() +":" + input.getDreamValueNo();
        RLock rLock = redisson.getLock(lockKey);
        RedissonRedLock redissonLock = new RedissonRedLock(rLock);
        try{
            if(redissonLock.tryLock()){

                //1. 幂等校验
                //1.1 计算分表后缀
                Integer suffix = subTableUtil.getTableSuffix(input.getDreamValueNo());
                //1.2 查询是否已经做过调减
                Integer subtractResult = dreamValueDao.queryDetailIsExistByTransNo(suffix,input.getTransNo(), DreamValueType.FLOW_TYPE_SUBTRACT.getCode(),input.getType());
                if(null != subtractResult){
                    throw new DreamValueExcption(ResultStatus.REPEAT_REQUEST);
                }
                //2.判断梦想值是否够扣
                Integer balance = dreamValueDao.queryDreamValue(suffix,input.getDreamValueNo(),input.getType());
                if(input.getDreamValue() > balance){
                    throw new DreamValueExcption(ResultStatus.DREAMVALUE_NOT_ENOUGH);
                }
                //3.更新mysql表中梦想值
                //调减梦想值余额
                dreamValueDao.updateDreamValue(suffix,-input.getDreamValue(),input.getType(),input.getDreamValueNo());
                //调减梦想值总额
                //dreamValueDao.updateDreamValueBalance(suffix,-input.getDreamValue(),input.getDreamValueNo());
                Integer balanceResult = balance - input.getDreamValue();
                try{
                    //4.更新redis中梦想值,发生异常不能影响数据库事务
                    String redisKey = RedisConstant.DREAM_VALUE_BALANCE_REIDS_KEY_PRIFIX + input.getDreamValueNo();
                    redisOpsUtil.set(redisKey,balanceResult,60*60, TimeUnit.SECONDS);
                }catch (Exception e){
                    log.error("调减接口更新redis时发生异常：{}",e.getMessage());
                }
                //5.往mysql中插入流水记录
                //获取流水号
                String flowNo = dreamValueNoGenerateUtil.generateDreamValueFlowNo(DreamValueType.FLOW_TYPE_SUBTRACT.getCode());
                //构建流水
                CustDreamValFlow flow = new CustDreamValFlow();
                flow.setFlowNo(flowNo);
                flow.setSuffix(suffix);
                copyDreamValueAddProperty(input,flow);
                //记录流水
                dreamValueDao.insertDreamValueFlow(flow);
                //6.往hbase中插入流水记录
                hbaseService.insertCustDreamValFlow(flow);
                //7. 设置返回参数
                result.put("dreamValueNo",input.getDreamValueNo());
                result.put("type","01");
                result.put("dreamValue",balanceResult.toString());
            }
        }catch (Exception e){
            log.error("调减时发生异常，异常信息为:{}",e.getMessage());
            throw e;

        }finally {
            redissonLock.unlock();
        }
        return CommonResult.success(result);
    }

    /**
     　* @description: 拷贝dto属性到流水实体
     　* @param [dto, flow]
     　* @return void
     　* @author 11292
     　* @date 2020/5/9 11:36
     */
    private void copyDreamValueAddProperty(DreamValueSubtractInDTO input, CustDreamValFlow flow) throws Exception {
        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                // 解析date+time
                .appendPattern("yyyyMMddHHmmss")
                // 解析毫秒数
                .appendValue(ChronoField.MILLI_OF_SECOND, 2)
                .toFormatter();
        flow.setDreamAcctNo(input.getDreamValueNo());
        flow.setDreamAcctType(input.getType());
        flow.setDreamVal(-input.getDreamValue());
        flow.setInsertTime(new Date());
        flow.setOperType(DreamValueType.FLOW_TYPE_SUBTRACT.getCode());
        flow.setTxDate(input.getTransDate());
        flow.setTxTime(input.getTransTime());
        flow.setTxSeqNo(input.getTransNo());
        flow.setTxDesc(URLDecoder.decode(input.getTransDesc(),"utf-8"));
        try{
            ZoneId zone = ZoneId.systemDefault();
            LocalDateTime dateTime = LocalDateTime.parse(input.getTransDate()+input.getTransTime(),formatter);
            Instant instant = dateTime.atZone(zone).toInstant();
            Date transDate = Date.from(instant);
            flow.setTxDateTime(transDate);
        }catch (Exception e){
            log.error("调增格式化时间时发生异常：{}",e.getMessage());
            throw  new DreamValueExcption(ResultStatus.DATE_FORMAT_ERROR);
        }
    }

    /**
     　* @description: 查询客户完成进度
     　* @param [input]
     　* @return cn.sunline.dreamvalue.constant.CommonResult<cn.sunline.dreamvalue.dto.DreamValueTaskOutDTO>
     　* @author 82345
     　* @date 2020/5/14 12:12
     */
    @Override
    public CommonResult<DreamValueTaskOutDTO> querySchedule(DreamAcctOpenDTO input) throws Exception {

        //定义返回数据体
        DreamValueTaskOutDTO outDTO = new DreamValueTaskOutDTO();
        //必填字段校验
        if(StringUtils.isEmpty(input.getDreamValueNo()) || StringUtils.isEmpty(input.getDreamAcctType())||StringUtils.isEmpty(input.getType())){
            return CommonResult.result(ResultStatus.PARAMETER_REQUIRED_FAILED,outDTO);
        }

        //hbase取数逻辑
        outDTO = hbaseService.querySchedule(input);

        return CommonResult.success(outDTO);
    }


    /**
     　* @description: 具体调增逻辑实现
     　* @param [dto]
     　* @return void
     　* @author 11292
     　* @date 2020/5/9 11:48
     * @return
     */
    @Transactional(rollbackFor = Throwable.class)
    @Override
    public CommonResult<Map<String, String>> dreamValueAdd(DreamValueAddInDTO dto) throws Exception {
        //定义返回体
        Map<String,String> result = new HashMap<String,String>();
        //检查消息体格式
        if(null == dto || StringUtils.isEmpty(dto.getDreamValueNo()) || null == dto.getDreamValue() || dto.getDreamValue() == 0
                || StringUtils.isEmpty(dto.getDate()) || StringUtils.isEmpty(dto.getTime()) ||
                StringUtils.isEmpty(dto.getType()) || StringUtils.isEmpty(dto.getTransNo()) || StringUtils.isEmpty(dto.getTransDesc())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_REQUIRED_FAILED);//消息体有错误
        }
        //梦想账号类型校验
        if(!dto.getType().trim().equals(DreamValueType.DEFALUT_TYPE.getCode())){
            throw new DreamValueExcption(ResultStatus.PARAMETER_VALUE_INCORRECT);
        }
        //增加调减接口，调减梦想值不能为负数校验，防止刷接口
        if(dto.getDreamValue() < 0){
            throw new DreamValueExcption(ResultStatus.ADD_DREAMVALUE_LESS_THAN_0_FAILED);
        }
        //校验梦想账户是否存在
        if(StringUtils.isNotEmpty(dto.getDreamValueNo())){
            Map<String,String> dreamAcct = dreamAcctOpenDao.selectDreamAcctByDreamValuNo(subTableUtil.getTableSuffix(dto.getDreamValueNo()),dto.getDreamValueNo());
            if(null == dreamAcct || dreamAcct.isEmpty()){
                throw new DreamValueExcption(ResultStatus.CUSTOMER_NOT_EXISTS_FAILED);
            }
        }
        //以下业务处理防止并发问题，增加分布式锁
        String lockKey = RedisConstant.DISTRIBUTED_LOCK_ADD_REIDS_KEY_PRIFIX + dto.getType() +":"+ dto.getDreamValueNo();
        RLock rLock = redisson.getLock(lockKey);
        RedissonRedLock redissonLock = new RedissonRedLock(rLock);
        try {
            if (redissonLock.tryLock()) {
                //计算分表后缀
                Integer suffix = subTableUtil.getTableSuffix(dto.getDreamValueNo());
                //查询是否已经做过调增
                Integer resFlg = dreamValueDao.queryDetailIsExistByTransNo(suffix,dto.getTransNo(), DreamValueType.FLOW_TYPE_ADD.getCode(),dto.getType());
                if(null != resFlg){
                    throw new DreamValueExcption(ResultStatus.REPEAT_REQUEST);//已经做过调增
                }
                //调增梦想值
                dreamValueDao.updateDreamValue(suffix,dto.getDreamValue(),dto.getType(),dto.getDreamValueNo());
                //调增梦想值余额
                dreamValueDao.updateDreamValueBalance(suffix,dto.getDreamValue(),dto.getDreamValueNo());
                //获取流水号
                String flowNo = dreamValueNoGenerateUtil.generateDreamValueFlowNo(DreamValueType.FLOW_TYPE_ADD.getCode());
                //构建流水
                CustDreamValFlow flow = new CustDreamValFlow();
                flow.setFlowNo(flowNo);
                flow.setSuffix(suffix);
                copyDreamValueAddProperty(dto,flow);
                log.info("=============>>>>>>>调增流水为{}",flow.toString());
                //记录流水到mysql
                dreamValueDao.insertDreamValueFlow(flow);
                //记录流水到hyperbase
                hbaseService.insertCustDreamValFlow(flow);
                Long balance = 0L;
                try{
                    //更新redis中梦想值
                    String redisKey = RedisConstant.DREAM_VALUE_BALANCE_REIDS_KEY_PRIFIX + dto.getDreamValueNo();
                    Integer redisBalance = redisOpsUtil.get(redisKey, Integer.class);
                    if(null != redisBalance){
                        balance = redisOpsUtil.incr(redisKey,dto.getDreamValue());
                        redisOpsUtil.expire(redisKey,60*60,TimeUnit.SECONDS);
                        result.put("balance",balance.toString());
                    }else{
                        //查询梦想值并放到缓存中去
                        //查询数据库
                        Integer dbBalance = dreamValueDao.queryDreamValue(subTableUtil.getTableSuffix(dto.getDreamValueNo()),dto.getDreamValueNo(),dto.getType());
                        if(null != dbBalance){
                            //先放入redis中
                            redisOpsUtil.set(redisKey,dbBalance,60*60, TimeUnit.SECONDS);
                            result.put("balance",dbBalance.toString());
                        }
                    }

                }catch (Exception e){
                    log.error("调增接口更新redis出错：{}",e.getMessage());
                }
            }
        }catch (Exception e){
            log.error("处理调增是发生异常：{}",e.getMessage());
            throw  e;
        }finally {
            redissonLock.unlock();
        }
        return CommonResult.success(result);
    }


    /**
     　* @description: 拷贝dto属性到流水实体
     　* @param [dto, flow]
     　* @return void
     　* @author 11292
     　* @date 2020/5/9 11:36
     */
    private void copyDreamValueAddProperty(DreamValueAddInDTO dto, CustDreamValFlow flow) throws Exception {
        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                // 解析date+time
                .appendPattern("yyyyMMddHHmmss")
                // 解析毫秒数
                .appendValue(ChronoField.MILLI_OF_SECOND, 2)
                .toFormatter();
        flow.setDreamAcctNo(dto.getDreamValueNo());
        flow.setDreamAcctType(dto.getType());
        flow.setDreamVal(dto.getDreamValue());
        try{
            ZoneId zone = ZoneId.systemDefault();
            LocalDateTime dateTime = LocalDateTime.parse(dto.getDate()+dto.getTime(),formatter);
            Instant instant = dateTime.atZone(zone).toInstant();
            Date transDate = Date.from(instant);
            flow.setTxDateTime(transDate);
        }catch (Exception e){
            log.error("调增格式化时间时发生异常：{}",e.getMessage());
            throw  new DreamValueExcption(ResultStatus.DATE_FORMAT_ERROR);
        }
        flow.setInsertTime(new Date());
        flow.setOperType(DreamValueType.FLOW_TYPE_ADD.getCode());
        flow.setTxDate(dto.getDate());
        flow.setTxTime(dto.getTime());
        flow.setTxSeqNo(dto.getTransNo());
        flow.setTxDesc(URLDecoder.decode(dto.getTransDesc(),"utf-8"));

    }

}
